package org.windowkit.toolkit.widget.text;



import java.text.ParseException;

import javax.swing.text.MaskFormatter;

/**
 * Formats a phone number for a formatted text field.
 *
 * @author Eric Lundin
 *
 */
public class PhoneNumberMaskFormatter extends MaskFormatter {

	/**
	 *
	 */
	private static final long serialVersionUID = -6962173158342415577L;

	// Regular exrpession to remove all non-numeric characters.
	private static final String STRIP_NON_NUMERIC_EXPRESSION = "[^\\p{Digit}]";

	// The mask to use to format this phone number.
	private static final String PHONE_NUMBER_FORMAT_MASK = "(###) ###-#### x#####";

	// The minimum number of digits required to accept a phone number as valid.
	private int minimumDigits;

	/**
	 *
	 * @param minimumDigits
	 *            The minimum number of digits that compose a "valid" phone
	 *            number.
	 */
	public PhoneNumberMaskFormatter(final int minimumDigits) {

		super();
		try {
			super.setMask(PHONE_NUMBER_FORMAT_MASK);
		} catch (ParseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		setPlaceholder(PHONE_NUMBER_FORMAT_MASK.replace("#", "_"));
		setPlaceholderCharacter('_');
		setCommitsOnValidEdit(true);
		setValueContainsLiteralCharacters(true);
		this.minimumDigits = minimumDigits;
	}

	@Override
	public Object stringToValue(final String value) throws ParseException {
		Object returnValue;
		String mask = PHONE_NUMBER_FORMAT_MASK;
		if (value.length() < getMinimumDigits()) {
			return null;
		}
		if (mask != null) {
			setMask(getMaskForString(mask, value));
			returnValue = super.stringToValue(value.substring(0, getMask()
					.length()));
			setMask(mask);
		}
		else {
			returnValue = super.stringToValue(value);
		}
		return returnValue;
	}

	/**
	 * Gets the mask to use for the given string. This allows us to have
	 * variable length phone numbers i.e. 10 digits plus an optional variable
	 * length extension.
	 *
	 * @param mask
	 *            The starting mask;
	 * @param value
	 *            The current value of of the text field.
	 * @return The mask to use in this case via getMask().
	 * @see getMask()
	 */
	protected String getMaskForString(final String mask, final String value) {
		mask.length();
		if (getNumberNumericDigits(value) < getMinimumDigits()) {
			return getMask(mask, getNumberOfOccurances(mask, '#')
					- getMinimumDigits());
		}
		else {
			return getMask(mask, getNumberOfOccurances(mask, '#')
					- (getNumberNumericDigits(value)));
		}
	}

	/**
	 * Gets the mask for the given number of currently required digits.
	 *
	 * @param mask
	 *            The original mask.
	 * @param number
	 *            The number of digits we wish to make optional.
	 * @return The mask to use.
	 */
	private String getMask(final String mask, final int number) {
		StringBuilder maskBuilder = new StringBuilder(mask);
		maskBuilder.trimToSize();
		maskBuilder = maskBuilder.reverse();
		for (int i = 0; i < number; i++) {
			maskBuilder = maskBuilder.replace(maskBuilder.indexOf("#"),
					maskBuilder.indexOf("#") + 1, "_");
		}
		maskBuilder.trimToSize();
		return maskBuilder.reverse().toString();
	}

	/**
	 * @param value
	 * @return
	 */
	private static int getNumberNumericDigits(final String value) {
		int count = 0;
		for (int i = 0; i < value.length(); i++) {
			if (Character.isDigit(value.charAt(i))) {
				++count;
			}
		}
		return count;
	}

	/**
	 * Counts the number of occurances of a given character.
	 *
	 * @param c
	 *            The character to look for.
	 * @return The number of times that character is found.
	 */
	private static int getNumberOfOccurances(final String string, final char c) {
		int count = 0;
		for (int i = 0; i < string.length(); i++) {
			if (string.charAt(i) == c) {
				++count;
			}
		}
		return count;
	}

	@Override
	public String valueToString(final Object value) throws ParseException {
		String returnValue = getPlaceholder();
		String mask = PHONE_NUMBER_FORMAT_MASK;
		String inputValue = (String) value;
		if (inputValue == null) {
			return getPlaceholder();
		}
		inputValue = ((String) value).replaceAll(STRIP_NON_NUMERIC_EXPRESSION,
				"");
		if (inputValue.length() < getMinimumDigits()) {
			return getPlaceholder();
		}
		for (int i = 0; i < inputValue.length(); i++) {
			returnValue = returnValue.replaceFirst(String
					.valueOf(getPlaceholderCharacter()), String
					.valueOf(inputValue.charAt(i)));
		}
		if (mask != null) {
			setMask(getMaskForString(mask, returnValue));
			returnValue = super.valueToString(returnValue.substring(0,
					getMask().length()));
			setMask(mask);
		}
		else {
			returnValue = super.valueToString(returnValue);
		}
		return returnValue;
	}

	/**
	 * Returns the minimum number of digits which must comprise this phone
	 * number.
	 *
	 * @return The minimum number of digits to allow this phone number.
	 */
	private int getMinimumDigits() {
		return minimumDigits;
	}

	/**
	 * Sets the minimum number of digits. For example to require at least the
	 * are code specify 3. To require a phone number with an optional extension
	 * specify 10.
	 *
	 * @param minimumDigits
	 *            The minimum number of digits needed to be valid.
	 */
	public void setMinimumNumberOfDigits(final int minimumDigits) {
		this.minimumDigits = minimumDigits;
	}
}
